﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;
#if !WINDOWS_PHONE
using System.Collections.Concurrent;

#else
using StyleMVVM.Data.Collections;

#endif

namespace StyleMVVM.Data
{
	public class NPCPipelineHandler : IExportPipelineHandler
	{
		private static string supplemental = typeof(NPCPipelineHandler).FullName;

		private ConcurrentDictionary<Type, List<WeakReference>> knownObject =
			new ConcurrentDictionary<Type, List<WeakReference>>();

		private Dictionary<Type, List<Type>> listenerTypes = new Dictionary<Type, List<Type>>();
		private Dictionary<Type, List<Type>> broadCastTypes = new Dictionary<Type, List<Type>>();

		public IEnumerable<IExportStrategy> FilterStrategiesForPipeline(IEnumerable<IExportStrategy> strategies)
		{
			List<IExportStrategy> returnValue = new List<IExportStrategy>();

			foreach (IExportStrategy exportStrategy in strategies)
			{
				Inspect(exportStrategy);
			}

			foreach (IExportStrategy exportStrategy in strategies)
			{
				if (listenerTypes.ContainsKey(exportStrategy.ActivationType) ||
				    broadCastTypes.ContainsKey(exportStrategy.ActivationType))
				{
					returnValue.Add(exportStrategy);
				}
			}

			return returnValue;
		}

		public void ProcessExport(object exportedObject, IExportStrategy strategy)
		{
			bool addToKnown = false;
			List<Type> listensTo;
			Type newObjectType = exportedObject.GetType();

			if (listenerTypes.TryGetValue(newObjectType, out listensTo))
			{
				foreach (Type type in listensTo)
				{
					List<WeakReference> broadCastReferences;

					if (knownObject.TryGetValue(type, out broadCastReferences))
					{
						foreach (WeakReference broadCastReference in broadCastReferences)
						{
							INotifyPropertyChanged notifyPropertyChanged = broadCastReference.Target as INotifyPropertyChanged;

							if (notifyPropertyChanged != null)
							{
								Type npcForwarderType = typeof(NPCForwarder<>).MakeGenericType(type);

								Activator.CreateInstance(npcForwarderType, notifyPropertyChanged, exportedObject);
							}
						}
					}
				}

				addToKnown = true;
			}

			List<Type> broadCastList;

			if (broadCastTypes.TryGetValue(newObjectType, out broadCastList))
			{
				Type npcForwarderType = null;

				foreach (Type type in broadCastList)
				{
					List<WeakReference> listeningReferences;

					if (knownObject.TryGetValue(type, out listeningReferences))
					{
						foreach (WeakReference listeningReference in listeningReferences)
						{
							object target = listeningReference.Target;

							if (target == null)
							{
								continue;
							}

							if (npcForwarderType == null)
							{
								npcForwarderType = typeof(NPCForwarder<>).MakeGenericType(newObjectType);
							}

							Activator.CreateInstance(npcForwarderType, exportedObject, target);
						}
					}
				}

				addToKnown = true;
			}

			if (addToKnown)
			{
				knownObject.AddOrUpdate(newObjectType,
				                        x => new List<WeakReference> { new WeakReference(exportedObject) },
				                        (x, y) =>
					                        {
						                        List<WeakReference> returnValue = new List<WeakReference>();

						                        foreach (WeakReference reference in y)
						                        {
							                        if (reference.IsAlive)
							                        {
								                        returnValue.Add(reference);
							                        }
						                        }

						                        return returnValue;
					                        });
			}
		}

		public void Inspect(IExportStrategy strategy)
		{
			foreach (Type implementedInterface in strategy.ActivationType.GetTypeInfo().ImplementedInterfaces)
			{
				if (implementedInterface.IsConstructedGenericType &&
				    implementedInterface.GetGenericTypeDefinition() == typeof(INotifyPropertyChangedListener<>))
				{
					List<Type> listenerList;
					Type broadCastType = implementedInterface.GenericTypeArguments[0];

					if (!listenerTypes.TryGetValue(strategy.ActivationType, out listenerList))
					{
						listenerList = new List<Type>();

						listenerTypes.Add(strategy.ActivationType, listenerList);
					}

					listenerList.Add(broadCastType);

					List<Type> broadCastList;

					if (!broadCastTypes.TryGetValue(broadCastType, out broadCastList))
					{
						broadCastList = new List<Type>();

						broadCastTypes.Add(broadCastType, broadCastList);
					}

					broadCastList.Add(strategy.ActivationType);
				}
			}
		}

		private class NPCForwarder<T>
		{
			private WeakReference listenerReference;

			public NPCForwarder(INotifyPropertyChanged changed, INotifyPropertyChangedListener<T> listener)
			{
				changed.PropertyChanged += ChangedOnPropertyChanged;
				listenerReference = new WeakReference(listener);
			}

			private void ChangedOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
			{
				INotifyPropertyChangedListener<T> listener = listenerReference.Target as INotifyPropertyChangedListener<T>;

				if (listener != null)
				{
					try
					{
						listener.ExternalPropertyChanged((T)sender, propertyChangedEventArgs.PropertyName);
					}
					catch (Exception exp)
					{
						Logger.Error("Exception thrown while forwarding property changed event: " +
						             sender.GetType().FullName + "." + propertyChangedEventArgs.PropertyName,
						             supplemental,
						             exp);
					}
				}
				else
				{
					INotifyPropertyChanged changed = sender as INotifyPropertyChanged;

					changed.PropertyChanged -= ChangedOnPropertyChanged;
				}
			}
		}
	}
}